interface Options {
    url: string,
    timeout?: number,

    getToken(): Promise<any>,

    method?: 'GET' | 'POST',

    [propName: string]: any
}

interface ResData {
    body: any,
    header: { type: string }
}

import parseQueryStr from './../../utils/parseQueryStr';

export const parseData = (data: any): ResData => (!!data ? JSON.parse(data) : {});

class Socket {
    private _url: string = ``;
    private _options: any = {};
    //心跳检测次数
    private _pingCount: number = 0;
    //连接超时时间
    private _timeout: number = 0;
    //心跳发送开始时间
    private _sendStart: number = 0;
    //是否手动调用关闭
    private _useClosing: boolean = false;

    private _getToken: Function;
    private _socket: WebSocket | any;
    private _destroyed: boolean = false;
    private _method: 'GET' | 'POST' = 'POST';
    private _events: any = {
        ping: [],
        pong: [],
        open: [],
        error: [],
        close: [],
        message: []
    }

    private _timer: any = {
        success: null,
        connecting: null,
        //心跳轮训时间间隔
        heartInterval: null,
        //心跳等待超时时间
        pingTimeout: null,
        //重连间隔时间
        reConnectTimer: null
    }

    private _resetCount: number = 0;

    constructor(options: Options) {
        const {
            url,
            method,
            getToken,
            timeout,
            ...rest
        } = options || {} as Options;

        this._url = url || ``;
        this._options = rest;
        this._getToken = getToken;
        this._method = method || 'POST';
        this._timeout = timeout || 15 * 1000;
        this._init();
    }

    private async _init() {
        await this._connecting();
        this._bindOnError();
        this.on(`open`, this._onOpenSuccess);
        this.on(`message`, this._listenMessage)
    }

    private async _await(): Promise<any> {
        this._timer.connecting = (
            setTimeout(
                this._connectTimeout,
                this._timeout
            )
        )
        return Promise.resolve();
    }

    //获取Token
    private async _getTokenString() {
        const _typeOf = typeof this._getToken as string;
        if (_typeOf.toLocaleUpperCase() === `FUNCTION`) {
            try {
                return (
                    await this._getToken()
                )
            }
            catch (e) {
                return Promise.resolve();
            }
        }
    }

    private async _connecting() {
        this._clearTimers();

        //获取Token放入headers
        const _token = (
            await this._getTokenString()
        )

        const url: string = (
            `${this._url}?${parseQueryStr({
                token: _token
            })}`
        )

        this._socket = (
            new WebSocket(url, this._method)
        )

        //设置连接超时
        await this._await();
        this._addEventListener();
        this._useClosing = false;

        const _msg = this._resetCount <= 0 ? `连接中...` :
            `尝试连接次数：${this._resetCount}`;
        this._pointLog(_msg);
    }

    private _pointLog(msg: string) {
        console.log(msg)
    }

    //监听长连接事件
    private _addEventListener(): void {
        Object.keys(this._events).forEach((key: string) => (
            this._socket[`on${key}`] = (e: any) => this._emit(key, e)
        ))
    }

    //错误监听
    private _bindOnError() {
        this.on(`error`, this._onOpenError);
        this.on(`close`, this._onOpenClosed);
    }

    private _onConnectClosed = () => {
        const _ts = 3 * 1000;
        this._timer.reConnectTimer = (
            setTimeout(this._resetCon, _ts)
        )
        this._pointLog(`连接被关闭，即将重新连接...`);
    }

    //监听连接成功
    private _onOpenSuccess() {
        const _ts = 500;
        this._connectHeart();
        this._clearConTimeout();
        this._timer.success = (
            setTimeout(() => {
                this._resetCount = 0;
            }, _ts)
        )
        this._pointLog(`连接成功...`);
    }

    //监听消息
    private _listenMessage({data}: any) {
        const {
            header,
            body
        } = parseData(data);

        const {
            type
        } = header;

        //如果为心跳
        switch (type) {
            case `ping`: {
                return this._emit(`pong`, body)
            }
        }
    }

    //建立心跳检测
    private _connectHeart() {
        const _ts = 10 * 1000; //10s
        this._timer.heartInterval = (
            setTimeout(this._sendHeart, _ts)
        )
    }

    //发送心跳
    private _sendHeart = () => {
        const _ts = 2.5 * 1000;
        const _send = (
            JSON.stringify({
                type: `ping`
            })
        )
        this._socket.send(_send);
        this.on(`pong`, this._receiveHeart);
        this._timer.pingTimeout = (
            setTimeout(this._sendHeartTimeout, _ts)
        )
        this._emit(`ping`);
        this._sendStart = Date.now();
        this._pointLog(`ping...`);
    }

    //收到心跳返回
    private _receiveHeart = () => {
        this._pingCount = 0;
        this._connectHeart();
        this.off(`pong`, this._receiveHeart);

        if (!!this._timer.pingTimeout) {
            clearTimeout(this._timer.pingTimeout)
        }
        const _ms: number = Date.now() - this._sendStart;
        this._pointLog(`pong... ${_ms}ms`);
    }

    //心跳超时
    private _sendHeartTimeout = () => {
        this._pingCount += 1;
        this.off(`pong`, this._receiveHeart);
        //如果大于等于3次关闭连接
        if (this._pingCount >= 3) {
            this._close().then(this._onConnectClosed);
            return this._pointLog(`心跳大于三次未返回，关闭连接!`);
        }
        this._connectHeart();
    }

    //监听连接失败
    private _onOpenError(e: any) {
        //失败后关闭连接后重连
        if (!this._useClosing) {
            this._close().then(this._onConnectClosed);
        }
    }

    private _onOpenClosed = () => {
        if (!this._useClosing) {
            this._onConnectClosed();
        }
    }

    //清除连接超时
    private _clearConTimeout() {
        const {
            connecting
        } = this._timer;
        if (!!connecting) {
            clearTimeout(connecting);
            this._timer.connecting = null;
        }
    }

    //连接超时后重连
    private _connectTimeout = () => {
        //超时关闭连接后重新连接
        this._close().then(
            this._onConnectClosed
        );
    }

    //检测当前是否为打开状态
    private _checkOpen() {
        const {
            readyState
        } = this._socket;
        return readyState === 1;
    }

    //重置连接
    private _resetCon = () => {
        this._pingCount = 0;
        const isOpen = this._checkOpen();
        if (isOpen) {
            this._resetCount = 0;
        }
        else {
            this._resetCount++;
            this._connecting();
        }
    }

    private _fns(type: string): Array<Function> {
        return (this._events[type] || []) as Array<Function>;
    }

    private _emit(type: string, e?: any): this {
        this._fns(type).forEach(fn => fn.call(this, e));
        return this;
    }

    private _checkType(type: string): boolean {
        return !!type && (type in this._events);
    }

    private _clearFnsByType(type: string): this {
        if (this._checkType(type)) {
            this._events[type] = [];
        }
        return this;
    }

    //清除所有监听
    public _clearAllFns(): this {
        const fn = (type: string) => {
            this._clearFnsByType(type);
        }
        Object.keys(this._events).forEach(fn);
        return this;
    }

    private _getFnIndex(type: string, fn: Function): number {
        const fns = this._fns(type);
        return fns.findIndex(f => f === fn);
    }

    //主动关闭
    private _close = (): Promise<any> => {
        this._useClosing = true;
        return new Promise(resolve => {
            this._clearTimers();
            this._socket.close();
            resolve();
        })
    }

    //监听
    public on(type: string, fn: Function): this {
        if (this._checkType(type)) {
            this._fns(type).push(fn);
        }
        return this;
    }

    //取消
    public off(type: string, fn?: Function) {
        if (this._checkType(type)) {
            if (!!fn) {
                const fns = this._fns(type);
                const i = this._getFnIndex(type, fn);
                if (i > -1) fns.splice(i, 1);
            }
            else {
                this._clearFnsByType(type);
            }
        }
        return this;
    }

    //清除所有时间
    private _clearTimers() {
        const Keys = Object.keys(this._timer);
        Keys.forEach((key: string) => {
            const timer = this._timer[key];
            if (timer) {
                clearTimeout(timer);
                this._timer[key] = null;
            }
        })
    }

    //销毁
    public destroy() {
        if(!this._socket) {
            return false
        }
        if (!this._destroyed) {
            this._close();
            this._clearAllFns();
        }
        this._destroyed = true;
        this._pointLog(`连接已销毁..`)
    }
}

export default Socket;


